﻿using Extended.WPF.Core;
using Extended.WPF.Core.Native;
using Extented.UI.Core.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Extented.UI.Core.Native
{
    public class Chrome : ContentPresenter, IChrome
    {
        public static readonly DependencyProperty ForegroundProperty;
        public static readonly DependencyProperty FontSizeProperty;
        public static readonly DependencyProperty FontFamilyProperty;
        public static readonly DependencyProperty FontStretchProperty;
        public static readonly DependencyProperty FontStyleProperty;
        public static readonly DependencyProperty FontWeightProperty;
        static readonly DataTemplate emptyTemplate;
        static Chrome()
        {
            Type ownerType = typeof(Chrome);
            UseLayoutRoundingProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
            FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
            var brush = new SolidColorBrush(Colors.Black);
            brush.Freeze();
            emptyTemplate = Net45Detector.IsNet45() ? null : new DataTemplate().Do(x => x.Seal());
            ForegroundProperty = Control.ForegroundProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(brush, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).ForegroundChanged((Brush)args.NewValue)));
            FontSizeProperty = Control.FontSizeProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(Control.FontSizeProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).UpdateSubTree()));
            FontFamilyProperty = Control.FontFamilyProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(Control.FontFamilyProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).UpdateSubTree()));
            FontStretchProperty = Control.FontStretchProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(Control.FontStretchProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).UpdateSubTree()));
            FontStyleProperty = Control.FontStyleProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(Control.FontStyleProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).UpdateSubTree()));
            FontWeightProperty = Control.FontWeightProperty.AddOwner(ownerType,
                new FrameworkPropertyMetadata(Control.FontWeightProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.Inherits, (o, args) => ((Chrome)o).UpdateSubTree()));
            FlowDirectionProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(
                FlowDirection.LeftToRight, FrameworkPropertyMetadataOptions.None, (o, args) => ((Chrome)o).ChromeFlowDirectionChanged(o, args)));
            RecognizesAccessKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true, (d, e) => ((Chrome)d).OnRecognizesAccessKeyChanged((bool)e.OldValue, (bool)e.NewValue)));
            ContentProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata((d, e) => ((Chrome)d).OnContentChanged(e.OldValue, e.NewValue)));
            ThemeManager.TreeWalkerProperty.AddOwner(ownerType, new FrameworkPropertyMetadata((d, e) => ((Chrome)d).OnThemeChanged((ThemeTreeWalker)e.OldValue, (ThemeTreeWalker)e.NewValue)));
        }
        void OnThemeChanged(ThemeTreeWalker oldValue, ThemeTreeWalker newValue)
        {
            if (newValue == null)
                return;
            isTouchEnabled = newValue.IsTouch;
            if (Root != null)
                Root.IsTouchEnabled = isTouchEnabled;
        }
        bool isTouchEnabled;
        bool contextUnloaded;
        object renderContent;
        FrameworkRenderElementContext context;
        readonly Namescope namescope;
        RenderTemplate renderTemplate;
        RenderTemplateSelector renderTemplateSelector = RenderTemplateSelector.Default;
        bool recognizesAccessKey = true;
        object content = null;
        string contentStringFormat = null;
        internal bool isEnabledInternal = true;
        internal FlowDirection flowDirectionInternal = FlowDirection.LeftToRight;
        public new object Content
        {
            get { return content; }
            set { base.Content = value; }
        }
        public new bool RecognizesAccessKey
        {
            get { return recognizesAccessKey; }
            set { base.RecognizesAccessKey = value; }
        }
        public new string ContentStringFormat
        {
            get { return contentStringFormat; }
            set { base.ContentStringFormat = value; }
        }
        FrameworkRenderElementContext Context { get { return context ?? (context = CreateContext()).Do(x => CoerceValues(x)); } }
        public INamescope Namescope { get { return namescope; } }
        public IElementHost ElementHost { get { return namescope; } }
        public Chrome()
        {
            namescope = new Namescope(this);
            Unloaded += ChromeUnloaded;
            Loaded += ChromeLoaded;
            IsEnabledChanged += ChromeIsEnabledChanged;
        }
        void ChromeIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            isEnabledInternal = (bool)e.NewValue;
            if (Root != null)
                Root.CoerceValue(FrameworkRenderElementContext.IsEnabledPropertyKey);
        }
        void ChromeFlowDirectionChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            flowDirectionInternal = (FlowDirection)e.NewValue;
            if (Root != null)
                Root.CoerceValue(FrameworkRenderElementContext.FlowDirectionPropertyKey);
        }
        void CoerceValues(FrameworkRenderElementContext c)
        {
            c.CoerceValue(FrameworkRenderElementContext.IsEnabledPropertyKey);
            c.CoerceValue(FrameworkRenderElementContext.FlowDirectionPropertyKey);
        }
        void ChromeLoaded(object sender, RoutedEventArgs e)
        {
            if (contextUnloaded)
                InvalidateContext();
        }
        void ChromeUnloaded(object sender, RoutedEventArgs e)
        {
            DestroyContext();
            contextUnloaded = true;
        }
        public FrameworkRenderElementContext Root { get { return context; } }
        public Brush Foreground
        {
            get { return (Brush)GetValue(ForegroundProperty); }
            set { SetValue(ForegroundProperty, value); }
        }
        public double FontSize
        {
            get { return (double)GetValue(FontSizeProperty); }
            set { SetValue(FontSizeProperty, value); }
        }
        public FontFamily FontFamily
        {
            get { return (FontFamily)GetValue(FontFamilyProperty); }
            set { SetValue(FontFamilyProperty, value); }
        }
        public FontStretch FontStretch
        {
            get { return (FontStretch)GetValue(FontStretchProperty); }
            set { SetValue(FontStretchProperty, value); }
        }
        public FontStyle FontStyle
        {
            get { return (FontStyle)GetValue(FontStyleProperty); }
            set { SetValue(FontStyleProperty, value); }
        }
        public FontWeight FontWeight
        {
            get { return (FontWeight)GetValue(FontWeightProperty); }
            set { SetValue(FontWeightProperty, value); }
        }
        public RenderTemplate RenderTemplate
        {
            get { return renderTemplate; }
            set
            {
                renderTemplate = value;
                InvalidateContext();
            }
        }
        public RenderTemplateSelector RenderTemplateSelector
        {
            get { return renderTemplateSelector; }
            set
            {
                if (renderTemplateSelector == value)
                    return;
                renderTemplateSelector = value ?? RenderTemplateSelector.Default;
                InvalidateContext();
            }
        }
        protected RenderTemplate ActualRenderTemplate { get; private set; }
        public object RenderContent
        {
            get { return renderContent; }
            set
            {
                object oldContent = renderContent;
                renderContent = value;
                if (!object.Equals(oldContent, renderContent))
                    InvalidateContext();
                RenderContentChanged(oldContent, renderContent);
            }
        }
        protected virtual void RenderContentChanged(object oldContent, object newContent)
        {
            InvalidateMeasureAndVisual();
        }
        protected void InvalidateContext()
        {
            DestroyContext();
            InvalidateMeasureAndVisual();
        }
        void DestroyContext()
        {
            context.Do(ReleaseContext);
            context = null;
        }
        protected internal void InvalidateMeasureAndVisual()
        {
            InvalidateMeasure();
            InvalidateVisual();
        }
        protected virtual FrameworkRenderElementContext CreateContentInternal()
        {
            return ActualRenderTemplate.With(x => x.CreateContext(Namescope, ElementHost, ElementHost.TemplatedParent ?? ElementHost.Parent));
        }
        protected override IEnumerator LogicalChildren { get { return new MergedEnumerator(base.LogicalChildren, ElementHost.LogicalChildren); } }
        protected override int VisualChildrenCount { get { return base.VisualChildrenCount + ElementHost.ChildrenCount; } }
        public Visual RealChild { get { return base.VisualChildrenCount > 0 ? GetVisualChild(0) : null; } }
        protected override Visual GetVisualChild(int index)
        {
            int childrenCount = base.VisualChildrenCount;
            if (childrenCount == 0)
                return ElementHost.GetChild(index);
            return index < childrenCount ? base.GetVisualChild(index) : ElementHost.GetChild(index - childrenCount);
        }
        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            if (visualAdded != null && visualAdded == RealChild)
                InvalidateContext();
        }
        protected virtual void ForegroundChanged(Brush newValue)
        {
            UpdateSubTree();
        }
        protected virtual void UpdateSubTree()
        {
            context.Do(x => x.UpdateLayout(FREInvalidateOptions.UpdateLayout | FREInvalidateOptions.AffectsChildrenCaches));
        }
        bool? useDefaultStringTemplate;
#if DEBUGTEST
		public static bool? forcedUseDefaultStringTemplate;
#endif
        protected internal bool UseDefaultStringTemplate
        {
            get
            {
#if DEBUGTEST
				if (forcedUseDefaultStringTemplate.HasValue)
					return forcedUseDefaultStringTemplate.Value;
#endif
                return (useDefaultStringTemplate ?? (useDefaultStringTemplate =
                  !String.IsNullOrEmpty(ContentStringFormat)
                  || RecognizesAccessKey && (Content as string).If(x => x.IndexOf('_') > -1).ReturnSuccess())).Value;
            }
        }
        protected virtual void UpdateInnerPresenters()
        {
            useDefaultStringTemplate = null;
            UpdateSubTree();
        }
        FrameworkRenderElementContext CreateContext()
        {
            if (context != null)
                ReleaseContext(context);
            ActualRenderTemplate = RenderTemplateSelector.SelectTemplate(this, RenderContent);
            var instance = CreateContentInternal();
            InitializeContext(instance);
            return instance;
        }
        protected virtual void ReleaseContext(FrameworkRenderElementContext context)
        {
            context.Do(x => x.Release());
            ((Namescope)Namescope).RootElement = null;
        }
        protected virtual void InitializeContext(FrameworkRenderElementContext context)
        {
            ((Namescope)Namescope).RootElement = context;
            ActualRenderTemplate.Do(x => x.InitializeTemplate(context, Namescope, ElementHost, namescope));
        }
        public void GoToState(string stateName)
        {
            ((Namescope)Namescope).GoToState(stateName);
        }
        void IChrome.AddChild(FrameworkElement element)
        {
            AddLogicalChild(element);
            AddVisualChild(element);
        }
        void IChrome.RemoveChild(FrameworkElement element)
        {
            RemoveLogicalChild(element);
            RemoveVisualChild(element);
        }
        protected override Size MeasureOverride(Size availableSize)
        {
            if (Context == null)
                return new Size(0, 0);
            Context.Measure(availableSize);
            return Context.DesiredSize;
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (context == null)
                return new Size(0, 0);
            context.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            return finalSize;
        }
        protected override void OnRender(DrawingContext dc)
        {
            if (context == null)
            {
                InvalidateMeasureAndVisual();
                return;
            }
            context.Render(dc);
        }
        protected override void OnContentStringFormatChanged(string oldContentStringFormat, string newContentStringFormat) { contentStringFormat = newContentStringFormat; UpdateInnerPresenters(); }
        protected virtual void OnRecognizesAccessKeyChanged(bool oldValue, bool newValue) { recognizesAccessKey = newValue; UpdateInnerPresenters(); }
        protected virtual void OnContentChanged(object oldValue, object newValue) { content = newValue; UpdateInnerPresenters(); }
        protected override DataTemplate ChooseTemplate()
        {
            return emptyTemplate;
        }
        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            return new PointHitTestResult(this, hitTestParameters.HitPoint);
        }
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            this.PropagateEvent(e, RenderEvents.MouseDown);
        }
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);
            this.PropagateEvent(e, RenderEvents.MouseUp);
        }
        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseDown(e);
            this.PropagateEvent(e, RenderEvents.PreviewMouseDown);
        }
        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseUp(e);
            this.PropagateEvent(e, RenderEvents.PreviewMouseUp);
        }
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            this.PropagateEvent(e, RenderEvents.MouseEnter);
        }
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            this.PropagateEvent(e, RenderEvents.MouseLeave);
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            this.PropagateEvent(e, RenderEvents.MouseMove);
        }
    }
}
